/*!
 * \file exec.c
 * \brief Fichier d'exécution des instructions.
 * \author Ben Hamouda Ahmed
 * \version 1.0
 * \date 08 juin 2011
 */
#include <stdio.h>
#include "exec.h"
#include "error.h"

// TODO: faut vérifier toutes les inégalités qui lancent des erreurs : Stricte ou pas ainsi que les erreurs.


//! Calculer le code condition
/*!
 * \param r le résultat à tester
 * \return Condition_Code
 */
Condition_Code calculate_cc(int r) {
// if r>0 then CC_P( résultat positif), else r<0 then CC_N (résultat negatif) , else r=0 then CC_Z (résultat nul) else CC_U
	if (r>0) return CC_P;
	else if (r<0) return CC_N;
	else if (r==0) return CC_Z;
	else return CC_U;
}


//! Dire si la condition est convenable avec le code condition
/*!
 * \param c La Condition 
 * \param cc le code condition 
 * \param addr l'adresse de la condition 
 * \return vrai si la condition et le code condition sont compatibles
 */
bool verify_condition(Condition c, Condition_Code cc, unsigned addr) {

	switch (c) {
		// Pas de condition 
		case NC:
			return true;
	
		// Égal à 0
		case EQ:
			return cc == CC_Z;

		// Différent de 0
		case NE:
			return cc != CC_Z;

		// Strictement positif
		case GT:
			return cc == CC_P;

		// Positif ou nul
		case GE:
			return cc != CC_N;

		// Strictement négatif
		case LT:
			return cc == CC_N;

		// Négatif ou null
		case LE:
			return cc != CC_P;
	}

	error(ERR_CONDITION, addr);
	return false;
}


//! Calculer l'adresse
/*!
 * \param pmach la machine/programme en cours d'exécution
 * \param instr l'instruction à exécuter
 * \return l'adresse de l'instruction
 */
int get_adress(Machine *pmach, Instruction instr) {
	// Instruction à adressage indexé
	if (instr.instr_generic._indexed) 
		//Addr = ( RX ) + Offset 
		return pmach->_registers[instr.instr_indexed._rindex] + instr.instr_indexed._offset;
	

	// Sinon instruction à adressage absolue 
	//Addr = Abs
	return instr.instr_absolute._address;
}


//! Calculer la valeure
/*!
 * \param pmach la machine/programme en cours d'exécution
 * \param instr l'instruction à exécuter
 * \return la valeure immédiate ou par adresse
 */
int get_value(Machine *pmach, Instruction instr) {
	// l'opérande est une valeure immédiate
	if (instr.instr_generic._immediate) {
		return instr.instr_immediate._value;
	}

	// Sinon l'opérande est une adresse
	int adress = get_adress(pmach, instr);
	if (adress >= pmach->_datasize) error(ERR_SEGDATA, pmach->_pc-1); //Violation de taille du segment de données

	// Récupération de la valeur dans la mémoire
	return pmach->_data[adress];
}


//! Décodage et exécution d'une instruction illégale ILLOP
/*!
 * \param pmach la machine/programme en cours d'exécution
 * \param instr l'instruction à exécuter
 * \return faux 
 */
bool decode_exec_illop(Machine *pmach, Instruction instr) {
	error(ERR_ILLEGAL, pmach->_pc-1);
	return false;
}


//! Décodage et exécution d'une instruction NOP ( no operation)
/*!
 * \param pmach la machine/programme en cours d'exécution
 * \param instr l'instruction à exécuter
 * \return vrai
 */
bool decode_exec_nop(Machine *pmach, Instruction instr) {
	return true;
}


//! Décodage et exécution d'une instruction LOAD pour charger un registre
/*!
 * \param pmach la machine/programme en cours d'exécution
 * \param instr l'instruction à exécuter
 * \return vrai
 */
bool decode_exec_load(Machine *pmach, Instruction instr) {

	pmach->_registers[instr.instr_immediate._regcond] = get_value(pmach, instr);
	pmach->_cc = calculate_cc(pmach->_registers[instr.instr_immediate._regcond]);

	return true;
}

//! Décodage et exécution d'une instruction STORE pour ranger le contenu d'un registre
/*!
 * \param pmach la machine/programme en cours d'exécution
 * \param instr l'instruction à exécuter
 * \return vrai si l'opérande est une adresse sinon le programme s'arrete
 */
// 
bool decode_exec_store(Machine *pmach, Instruction instr) {
	if (instr.instr_generic._immediate) error(ERR_IMMEDIATE, pmach->_pc-1);

	pmach->_data[get_adress(pmach, instr)] = pmach->_registers[instr.instr_immediate._regcond];

	return true;
}

//! Décodage et exécution d'une instruction ADD pour additionner à un registre
/*!
 * \param pmach la machine/programme en cours d'exécution
 * \param instr l'instruction à exécuter
 * \return vrai
 */
// 
bool decode_exec_add(Machine *pmach, Instruction instr) {


	pmach->_registers[instr.instr_immediate._regcond] += get_value(pmach, instr);
	pmach->_cc = calculate_cc(pmach->_registers[instr.instr_immediate._regcond]);

	return true;
}

//! Décodage et exécution d'une instruction SUB pour soustraire d'un registre
/*!
 * \param pmach la machine/programme en cours d'exécution
 * \param instr l'instruction à exécuter
 * \return vrai
 */
// 
bool decode_exec_sub(Machine *pmach, Instruction instr) {
	
	pmach->_registers[instr.instr_immediate._regcond] -= get_value(pmach, instr);
	pmach->_cc = calculate_cc(pmach->_registers[instr.instr_immediate._regcond]);

	return true;
}


//! Décodage et exécution d'une instruction BRANCH pour brancher à une adresse
/*!
 * \param pmach la machine/programme en cours d'exécution
 * \param instr l'instruction à exécuter
 * \return vrai si l'opérande est une adresse + s'il n'y a pas d'erreurs de segmentation, sinon le programme s'arrete.
 */
// 
bool decode_exec_branch(Machine *pmach, Instruction instr) {
	// L'opérande doit être une adresse !
	if (instr.instr_generic._immediate) error(ERR_IMMEDIATE, pmach->_pc-1);

	//si la condition est convenable avec le code condition alors PC = Addr 
	if (verify_condition(instr.instr_generic._regcond, pmach->_cc, pmach->_pc-1)) {
		int adress = get_adress(pmach, instr);

		if (adress >= pmach->_textsize) error(ERR_SEGTEXT, pmach->_pc-1); // Violation de taille du segment de texte

		pmach->_pc = adress;
	}

	return true;
}

//! Décodage et exécution d'une instruction CALL pour appeler un sous-programme
/*!
 * \param pmach la machine/programme en cours d'exécution
 * \param instr l'instruction à exécuter
 * \return vrai si l'opérande est une adresse + s'il n'y a pas d'erreurs de segmentation, sinon le programme s'arrete.
 */
//
bool decode_exec_call(Machine *pmach, Instruction instr) {
	// L'opérande doit être une adresse !
	if (instr.instr_generic._immediate) error(ERR_IMMEDIATE, pmach->_pc-1);

	//si la condition est convenable avec le code condition
	if (verify_condition(instr.instr_generic._regcond, pmach->_cc, pmach->_pc-1)) {
		
		if (pmach->_sp >= pmach->_datasize) error(ERR_SEGDATA, pmach->_pc-1); //Violation de taille du segment de données

		pmach->_data[pmach->_sp] = pmach->_pc; //Data[(SP)] = ( PC )
		--pmach->_sp; //SP = (SP) − 1
		pmach->_pc = get_adress(pmach, instr); //PC = Addr
	}

	return true;
}


//! Décodage et exécution d'une instruction RET pour retourner d'un sous-programme
/*!
 * \param pmach la machine/programme en cours d'exécution
 * \param instr l'instruction à exécuter
 * \return vrai s'il n' y a pas d'erreurs de segmentation.
 */
//
bool decode_exec_ret(Machine *pmach, Instruction instr) {

	++pmach->_sp; //SP = (SP) + 1
	
	if (pmach->_sp >= pmach->_datasize) error(ERR_SEGDATA, pmach->_pc-1); //Violation de taille du segment de données

	int adress = pmach->_data[pmach->_sp];

	if (adress >= pmach->_textsize) error(ERR_SEGTEXT, pmach->_pc-1); //Violation de taille du segment de texte

	pmach->_pc = adress; //PC = Data[(SP)
	
	return true;
}

//! Décodage et exécution d'une instruction PUSH pour empiler dans la pile d'exécution.
/*!
 * \param pmach la machine/programme en cours d'exécution
 * \param instr l'instruction à exécuter
 * \return vrai s'il n' y a pas d'erreurs de segmentation.
 */ 
//
bool decode_exec_push(Machine *pmach, Instruction instr) {

	if (pmach->_sp >= pmach->_datasize) error(ERR_SEGSTACK, pmach->_pc-1); // Violation de taille du segment de pile

	//si I = 0 : Data[(SP)] = Data[ Addr ], si I = 1 : Data[(SP)] = Val
	pmach->_data[pmach->_sp] = get_value(pmach, instr);
	--pmach->_sp; // dans tous les cas : SP = (SP) − 1

	return true;
}

//! Décodage et exécution d'une instruction POP pour dépiler de la pile d'exécution.
/*!
 * \param pmach la machine/programme en cours d'exécution
 * \param instr l'instruction à exécuter
 * \return vrai si l'opérande est une adresse , sinon le programme s'arrete.
 */
//
bool decode_exec_pop(Machine *pmach, Instruction instr) {
	// L'opérande doit être une adresse !
	if (instr.instr_generic._immediate) error(ERR_IMMEDIATE, pmach->_pc-1);
	
	++pmach->_sp; // SP = (SP) + 1

	pmach->_data[get_value(pmach, instr)] = pmach->_data[pmach->_sp]; //Data[ Addr ] = Data[(SP)] 

	return true;
}

//! Décodage et exécution d'une instruction HALT pour arrêter le programme normalement.
/*!
 * \param pmach la machine/programme en cours d'exécution
 * \param instr l'instruction à exécuter
 * \return false.
 */
//
bool decode_exec_halt(Machine *pmach, Instruction instr) {
	// on lance une alerte pour dire que le programme est fini.
	warning(WARN_HALT, pmach->_pc-1);
	return false;
}

bool decode_execute(Machine *pmach, Instruction instr)
{

	switch(instr.instr_generic._cop) {
		case ILLOP : return decode_exec_illop(pmach, instr);
		case NOP : return decode_exec_nop(pmach, instr);
		case LOAD :return decode_exec_load(pmach, instr);
		case STORE :return decode_exec_store(pmach, instr);
		case ADD :return decode_exec_add(pmach, instr);
		case SUB :return decode_exec_sub(pmach, instr);
		case BRANCH :return decode_exec_branch(pmach, instr);
		case CALL :return decode_exec_call(pmach, instr);
		case RET :return decode_exec_ret(pmach, instr);
		case PUSH :return decode_exec_push(pmach, instr);
		case POP :return decode_exec_pop(pmach, instr);
		case HALT :return decode_exec_halt(pmach, instr);
		default: return decode_exec_illop(pmach, instr);
	}

}

void trace(const char *msg, Machine *pmach, Instruction instr, unsigned addr) {

	printf("TRACE: %s: 0x%04x: ", msg, addr);
	print_instruction(instr, addr);
	printf("\n");
}
